/*
CalendarView.m - Copyright (c) 1992 NeXT Computer, Inc.

You may freely copy, distribute and reuse the code in this example.
NeXT Computer, Inc. disclaims any warranty of any kind, expressed or implied, 
as to its fitness for any particular use.
*/
 
#import	<ansi/ansi.h>
#import	<bsd/libc.h>
#import <appkit/appkit.h> 
#import <dpsclient/psops.h> 
#import "CalendarView.h"

#define NROWS		6
#define NCOLS		7
#define VIEW_BORDER	3.0
#define CELL_SPACE	0.0 /* 2 */
#define ITEM_SPACE	2.0
#define TEXT_OFFSET	5.0
#define YEAR_RANGE	10
#define BACK_YEARS	3

static char *monthStrings[] = { 
	"January", "February", "March", "April", "May", "June",
	"July", "August", "September", "October", "November", "December" };
	
static char *dayStrings[] = { 
	"S", "M", "T", "W", "T", "F","S" };
	
static char *dayNumStrings[] = { 
	"1", "2", "3", "4", "5", "6","7","8","9","10",
	"11", "12", "13", "14", "15", "16","17","18","19","20",
	"21", "22", "23", "24", "25", "26","27","28","29","30",
        "31" };

char		myTimeZone[10];

/* compare two times of hh:mm for a <= b */
int timeLessThanOrEqual(lhour, ghour, lmin, gmin)
    int	lhour;
    int	ghour;
    int	lmin;
    int	gmin;
{
    if (lhour > ghour)
	return FALSE;
    if ((lhour == ghour) && (lmin > gmin))
	return FALSE;
    return TRUE;
}

int	todayDay()
{
    time_t	t;
    struct tm	*tp;
    
    time(&t);
    tp = localtime(&t);
    return (tp->tm_mday);
}

int isToday(year, month, day)
    int year;
    int month;
    int day;
{
    time_t	t;
    struct tm	*tp;
    
    year -= 1900;
    month--;
    time(&t);
    tp = localtime(&t);
    if ((year == tp->tm_year) && (month == tp->tm_mon) && (day == tp->tm_mday)) {
	return (1);
    } else {
	return (0);
    }
    return (tp->tm_mday);

}

int isThisMonthAndYear(year, month)
    int year;
    int month;
{
    time_t	t;
    struct tm	*tp;
    
    year -= 1900;
    month--;
    time(&t);
    tp = localtime(&t);
    if ((year == tp->tm_year) && (month == tp->tm_mon)) {
	return (1);
    } else {
	return (0);
    }
    return (tp->tm_mday);

}

void copyTime(dest, destZone, source)
    struct tm	*dest;
    char	*destZone;
    struct tm	*source;
{
    dest->tm_sec    = source->tm_sec;
    dest->tm_min    = source->tm_min;
    dest->tm_hour   = source->tm_hour;
    dest->tm_mday   = source->tm_mday;
    dest->tm_mon    = source->tm_mon;
    dest->tm_year   = source->tm_year;
    dest->tm_wday   = source->tm_wday;
    dest->tm_yday   = source->tm_yday;
    dest->tm_isdst  = source->tm_isdst;
    dest->tm_gmtoff = source->tm_gmtoff;
    if (source->tm_zone != NULL)
	strcpy(destZone, source->tm_zone);
    dest->tm_zone = destZone;
}
	
time_t myMakeTime(ntp)
    struct tm	*ntp;
{
    time_t	t;
    struct tm	*tp;
    int		saveHour;
    
    saveHour = ntp->tm_hour;
    t = mktime(ntp);
    tp = localtime(&t);
    copyTime(ntp, myTimeZone, tp);
    ntp->tm_hour = saveHour;
    t = mktime(ntp);
    tp = localtime(&t);
    copyTime(ntp, myTimeZone, tp);
    return t;
}

void getTime(ntp)
    struct tm	*ntp;
{
    time_t	t;
    struct tm	*tp;
    
    time(&t);
    tp = localtime(&t);
    copyTime(ntp, myTimeZone, tp);
}

void myLocalTime(ntp, t)
    struct tm	*ntp;
    time_t	*t;
{
    struct tm	*tp;
    
    tp = localtime(t);
    copyTime(ntp, myTimeZone, tp);
}

int weekDayOfFirstDayOfMonth(ntp)
    struct tm	*ntp;
{
    struct tm	*tp;
    time_t	t;
    struct tm	mt;
    char	tz[10];
    
    copyTime(&mt, tz, ntp); 
    mt.tm_mday = 1;   
    mt.tm_hour = 8;   
    t = mktime(&mt);
    tp = localtime(&t);
    return tp->tm_wday;
}
			   
@interface LabelCell:Cell
{
}
- drawSelf:(NXRect const *)r inView:controlView;
@end

@implementation LabelCell

- drawInside:(NXRect const *)r inView:controlView
{
    float	w;
    
    w = [support	getWidthOf:contents];
    [support	set];
    PSmoveto(r->origin.x + r->size.width/2.0 - w/2.0, r->size.height - TEXT_OFFSET);
    if (cFlags1.state)
	PSsetgray(NX_DKGRAY);
    else
	PSsetgray(NX_BLACK);
    PSshow(contents);
    return self;
}

- drawSelf:(NXRect const *)r inView:controlView
{
    [self	drawInside:r inView:controlView];
    return self;
}

@end

@interface DayCell:Cell
{
    BOOL		isDimmed;
}

- drawSelf:(NXRect const *)r inView:controlView;
- setDimmed:(BOOL)theValue;

@end

@implementation DayCell

- setDimmed:(BOOL)theValue
{
    isDimmed = theValue;
    return self;
}

- drawInside:(NXRect const *)r inView:controlView
{
    float	w;
    
    if (cFlags1.disabled) {
	NXDrawGrayBezel(r, r);
    } else {
	NXDrawWhiteBezel(r, r);
	if (cFlags1.highlighted  || cFlags1.state)
	    NXHighlightRect(r);
	w = [support	getWidthOf:contents];
	[support	set];
	PSmoveto(/* X */ r->origin.x + r->size.width/2.0 - w/2.0,
		  /* Y */ r->origin.y + r->size.height - TEXT_OFFSET);
	if (isDimmed)
	    PSsetgray(NX_DKGRAY);
	else
	    PSsetgray(NX_BLACK);
	PSshow(contents);
    }
    return self;
}

- drawSelf:(NXRect const *)r inView:controlView
{
    [self	drawInside:r inView:controlView];
    return self;
}

- highlight:(NXRect const *)r inView:controlView lit:(BOOL)flag
{
    if (cFlags1.highlighted != flag) {
	cFlags1.highlighted = flag;
	if (!flag && cFlags1.state)
	    return self;
	NXHighlightRect(r);
    }
    return self;
}

- read:(NXTypedStream*)s
{
    [super	read:s];
    NXReadTypes(s,"i", &isDimmed);
    return self;    
}

- write:(NXTypedStream*)s
{
    [super	write:s];
    NXWriteTypes(s,"i", &isDimmed);
    return self;    
}

@end

@implementation CalendarView

- initFrame:(NXRect const *)r
{
    NXRect	matrixRect;
    NXSize	size;
    int		i;
    char	buf[10];
    float	pos;
    id		theCell;
    
    [super	initFrame:r];
    matrixRect = *r;
    viewRect   = *r;
    matrixRect.size.height -= VIEW_BORDER * 2;
    matrixRect.size.width -= VIEW_BORDER * 2;
    cellSize.width  = floor((matrixRect.size.width - (CELL_SPACE * (NCOLS-1)))  / NCOLS);
    cellSize.height = floor((matrixRect.size.height  - (CELL_SPACE * (NCOLS-1))) / (NROWS+2));
    matrixRect.size.width   = cellSize.width * NCOLS;
    matrixRect.size.height  = NROWS * cellSize.height;
    //[self	sizeTo:matrixRect.size.width + (2 * VIEW_BORDER) :r->size.height];
    bounds.size.width = matrixRect.size.width + (2 * VIEW_BORDER);
    bounds.size.height = r->size.height;
    frame.size.width = matrixRect.size.width + (2 * VIEW_BORDER);
    frame.size.height = r->size.height;
    viewRect = bounds;
    
    monthMatrix    = [[Matrix	alloc] initFrame:&matrixRect 
                        mode:NX_RADIOMODE
			cellClass:[DayCell	class] 
			numRows:NROWS 
			numCols:NCOLS];
    size.width  = CELL_SPACE;
    size.height = CELL_SPACE;
    [monthMatrix	setBackgroundGray:NX_BLACK];
    [monthMatrix	setIntercell:&size];
    [monthMatrix	setCellSize:&cellSize];
    [monthMatrix	setEmptySelectionEnabled:NO];
    [self		addSubview:monthMatrix];
    [monthMatrix	moveTo:VIEW_BORDER:VIEW_BORDER];
    [monthMatrix	setTarget:self];
    [monthMatrix	setAction:@selector(dayChanged:)];
    
    
    monthPl = [[PopUpList alloc] init];
    for (i = 0; i<12; i++) {
	 [monthPl	addItem:monthStrings[i]];
    }
    monthButton = NXCreatePopUpListButton(monthPl);
    [self	addSubview:monthButton];
    [monthPl	setTarget:self];
    [monthPl	setAction:@selector(dateChanged:)];
			      
    getTime(&theTime);
    yearPl = [[PopUpList alloc] init];
    yearBase = theTime.tm_year - BACK_YEARS;
    for (i = 0; i<YEAR_RANGE; i++) {
   	 sprintf(buf, "%d", theTime.tm_year+1900+i-BACK_YEARS);
	 [yearPl	addItem:buf];
    }
    yearButton = NXCreatePopUpListButton(yearPl);
    [self	addSubview:yearButton];
    [yearPl	setTarget:self];
    [yearPl	setAction:@selector(dateChanged:)];
    [self	setFonts];
    [self	setupButtons];
    sprintf(buf, "%d", theTime.tm_year+1900);
    [yearButton	setTitle:buf];
    
    pos = matrixRect.size.height;
    matrixRect.size.height = cellSize.height - (2 * ITEM_SPACE) - VIEW_BORDER; 
    dayLabelMatrix    = [[Matrix alloc]	initFrame:&matrixRect  
                        mode:NX_TRACKMODE
			cellClass:[LabelCell	class] 
			numRows:1 
			numCols:NCOLS];
    size.width  = CELL_SPACE;
    size.height = CELL_SPACE;
    [dayLabelMatrix 	setBackgroundGray:NX_LTGRAY];
    [dayLabelMatrix 	setIntercell:&size];
    size.width  = cellSize.width;
    size.height = matrixRect.size.height;
    [dayLabelMatrix	setCellSize:&size];
    [self		addSubview:dayLabelMatrix];
    [dayLabelMatrix	moveTo:VIEW_BORDER :pos + ITEM_SPACE + VIEW_BORDER];
    for (i = 0; i<NCOLS; i++) {
	theCell = [dayLabelMatrix	cellAt:0:i];
    	[theCell	setStringValue:dayStrings[i]];
    	[theCell	setFont:buttonFont];
	[theCell	setState:(((i == 0) || (i == 6)) ? 1 : 0)];
    }
    [monthButton	setTitle:monthStrings[theTime.tm_mon]];
    [self	loadMatrix];
    useDelegate = NO;
    return self;
}

- setDelegate:anObject
{
    delegate = anObject;
    useDelegate = YES;
    return self;
}

- setDoDelegate:anObject
{
    delegate = anObject;
    return self;
}

-(BOOL)useDelegate
{
    return (useDelegate);
}

/*
** Check for correct view type and to see of delegate outlet not yet set.
** If so check for object in responder chain the responds to takeStringFrom:
** and set that object as our delegate.  However of the object has a superview
** that is of type CalendarView do not set delegate. 
*/ 
- (BOOL)acceptsFirstResponder
{
    id	temp;
    
    if ([self	useDelegate] == YES) {
	return NO;
    }
    
    temp = [window	firstResponder];
    while (temp) {
	if ([temp	respondsTo:@selector(takeStringValueFrom:)]) {
	    [self	setDoDelegate:temp];
	    temp = nil;
	} else {
	    temp = [temp	nextResponder];
	}
    }
    return YES;
}

- setFonts
{
    int	i;
    
    if (cellFont == NULL) {
	[cellFont	free];
	[buttonFont	free];
    }
    cellFont = [Font	newFont:"Helvetica"
                        size: (((MIN(cellSize.width, cellSize.height) 
			       - (TEXT_OFFSET * 2)) /2.0) * 2.0) 
			style:0 
			matrix:NX_FLIPPEDMATRIX];
    buttonFont = [Font	newFont:"Helvetica-Bold"
                        size: (((MIN(cellSize.width, cellSize.height) 
			       - (TEXT_OFFSET * 2)) /2.0) * 2.0) 
			style:0 
			matrix:NX_FLIPPEDMATRIX];
    for (i = 0; i < NCOLS; i++)
	[[dayLabelMatrix	cellAt:0:i]	setFont:buttonFont];
    return self;
}

- setupButtons
{
    NXRect	f;
    char	buf[20];
    
    strcpy(buf, [monthButton	title]);
    [monthButton	setTitle:"0000000001"];
    [monthButton	setFont:buttonFont];
    [monthPl		setFont:buttonFont];
    [monthButton	sizeToFit];
    [monthButton	setTitle:buf];
    [monthButton	getFrame:&f];
    [monthButton	moveTo:VIEW_BORDER+ITEM_SPACE 
			      :viewRect.size.height - f.size.height - VIEW_BORDER - ITEM_SPACE];
    strcpy(buf, [yearButton	title]);
    [yearButton	setTitle:"00001"];
    [yearButton		setFont:buttonFont];
    [yearPl		setFont:buttonFont];
    [yearButton		sizeToFit];
    [yearButton		setTitle:buf];
    [yearButton		getFrame:&f];
    [yearButton		moveTo:viewRect.size.width - f.size.width - VIEW_BORDER - ITEM_SPACE 
		              :viewRect.size.height - f.size.height - VIEW_BORDER - ITEM_SPACE];
    return self;
}

- drawSelf:(const NXRect *)rects :(int)rectCount
{
    PSsetgray(NX_DKGRAY);
    NXRectFill(rects);
    NXDrawGroove(rects, rects);
    return self;
}

int lastDay(month, year)
    int	month;
    int	year;
{
	switch (month) {
		case 1:
		if (((year % 4) == 0) && ((year % 400) != 0))
			return 29;
		else
			return 28;
	case 3:
	case 5:
	case 8:
	case 10:
		return 30;
	default:
		return 31;
	}
}

- loadMatrix
{
    int		row;
    int		col;
    int		fDay;
    int		lDay;
    int		day = 0;
    int		passedFirstDay = FALSE;
    id		theCell;

    fDay = weekDayOfFirstDayOfMonth(&theTime);
    lDay = lastDay(theTime.tm_mon, theTime.tm_year);
    for (row = 0; row < NROWS; row++) {
	for (col = 0; col < NCOLS; col++) {
	    theCell = [monthMatrix	cellAt:row:col];
	    if ((col == fDay) && (!passedFirstDay))
		passedFirstDay = TRUE;
	    [theCell	setDimmed:(((col == 0) || (col == 6)) ? YES : NO)];
	    if ((passedFirstDay) && (day < lDay)) {
		[theCell	setEnabled:YES];
		[theCell	setStringValue:dayNumStrings[day++]];
		[theCell	setFont:cellFont];
		if (day == theTime.tm_mday)
		    [monthMatrix	selectCellAt:row:col];	
	    } else {
		[theCell	setEnabled:NO];
	    }
	}
    }
    [monthMatrix	display];
    return self;
}

- (const char *)stringValue
{
    strftime(tbuf, 50, "%d-%B-%Y", &theTime);
    return (tbuf);
}

- dateChanged:sender
{
    int		lDay;
    int		temp;
    
    temp = [monthPl	indexOfItem:[monthPl	selectedItem]];
    if (temp != -1)
	theTime.tm_mon = temp;
    temp = [yearPl	indexOfItem:[yearPl	selectedItem]];
    if (temp != -1)
	theTime.tm_year = temp + yearBase;
    lDay = lastDay(theTime.tm_mon, theTime.tm_year);
    if (theTime.tm_mday > lDay) 
	theTime.tm_mday = lDay;
	
    t = myMakeTime(&theTime);
    [self	loadMatrix];
    if (useDelegate == YES) {
	if ([delegate	respondsTo:sel_getUid("dateDidChange:")])
	    [delegate	perform:sel_getUid("dateDidChange:") with:self];
    } else {
	[delegate	takeStringValueFrom:self];
    }
    return self;
}

- dayChanged:sender
{
    theTime.tm_mday = [[monthMatrix	selectedCell]	intValue];
    t = myMakeTime(&theTime);
    if (useDelegate == YES) {
	if ([delegate	respondsTo:sel_getUid("dateDidChange:")])
	    [delegate	perform:sel_getUid("dateDidChange:") with:self];
    } else {
	[delegate	takeStringValueFrom:self];
    }
    return self;
}

- (struct tm *)currentDate
{
    return (&theTime);
}

- setCurrentDate:(struct tm *)theDate
{
    char	buf[20];
    
    if (![self canDraw])
	return self;
    if (theDate->tm_year >= (yearBase + YEAR_RANGE)) {
	NXRunAlertPanel("Alert",  "Year is out of range in setCurrentDate", 0, 0, 0);    
	return self;
    }
    copyTime(&theTime, timeZone, *theDate);
    [monthButton	setTitle:monthStrings[theTime.tm_mon]];
    sprintf(buf, "%d", theTime.tm_year+1900);
    [yearButton		setTitle:buf];
    [self		loadMatrix];
    return self;
}

- sizeTo:(NXCoord)width :(NXCoord)height
{
    NXSize	matrixSize;
    NXSize	size;
    float	pos;
    
    matrixSize.height = height  - (VIEW_BORDER * 2);
    matrixSize.width =  width - (VIEW_BORDER * 2);
    cellSize.width  = floor((matrixSize.width - (CELL_SPACE * (NCOLS-1)))  / NCOLS);
    cellSize.height = floor((matrixSize.height  - (CELL_SPACE * (NCOLS-1))) / (NROWS+2));
    matrixSize.width   = cellSize.width * NCOLS;
    matrixSize.height  = NROWS * cellSize.height;
    [super	sizeTo:matrixSize.width + (2 * VIEW_BORDER) :height];
    viewRect = bounds;
    [monthMatrix	setCellSize:&cellSize];
    [monthMatrix	moveTo:VIEW_BORDER:VIEW_BORDER];
    [monthMatrix	sizeTo:matrixSize.width :matrixSize.height]; 
    
    pos = matrixSize.height;
    matrixSize.height = cellSize.height - (2 * ITEM_SPACE) - VIEW_BORDER; 
    size.width  = cellSize.width;
    size.height = matrixSize.height;
    [dayLabelMatrix	setCellSize:&size];
    [dayLabelMatrix	moveTo:VIEW_BORDER :pos + ITEM_SPACE + VIEW_BORDER];
    [dayLabelMatrix	sizeTo:matrixSize.width :matrixSize.height]; 
    [self	setFonts];
    [self	setupButtons];
    [self	loadMatrix];
    return self;
}

- read:(NXTypedStream*)s
{
    [super	read:s];
    NXReadTypes(s,"@@@@@@@@@i", &delegate, &monthButton, &monthPl, &yearButton, &yearPl, &monthMatrix, &dayLabelMatrix, &cellFont, &buttonFont, &useDelegate);
    getTime(&theTime);
    yearBase = theTime.tm_year - BACK_YEARS;
    [self	setCurrentDate:&theTime];
    return self;    
}

- write:(NXTypedStream*)s
{
    [super	write:s];
    NXWriteTypes(s,"@@@@@@@@@i", &delegate, &monthButton, &monthPl, &yearButton, &yearPl, &monthMatrix, &dayLabelMatrix, &cellFont, &buttonFont, &useDelegate);
    return self;    
}


@end
